Skip to content

Conversation

brandonspark
Copy link

@brandonspark brandonspark commented Aug 29, 2025

Motivation and Context

I am interested in being able to dynamically have a server offer only certain tools based on information received during its initialization. I could conditionally declare the tool, for instance:

if cond:
  @mcp.tool
  async def foo():
    ...

but I perceive this to be relatively gross. I would prefer to be able to remove a tool within the API.

How Has This Been Tested?

I can achieve comparable behavior by inserting an if before defining a function with the mcp.tool() decorator.

Breaking Changes

No.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@brandonspark brandonspark requested a review from a team as a code owner August 29, 2025 18:32
@brandonspark brandonspark requested a review from Kludex August 29, 2025 18:32
dsp-ant
dsp-ant previously approved these changes Sep 1, 2025
Copy link
Member

@dsp-ant dsp-ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is very reasonable. @Kludex opinions?

@@ -68,6 +68,13 @@ def add_tool(
self._tools[tool.name] = tool
return tool

def remove_tool(self, name: str) -> None:
"""Remove a tool by name."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably say a word about the beahviour if the tool doesn't exists.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should raise if not found.

@Kludex
Copy link
Member

Kludex commented Sep 1, 2025

I think this is very reasonable. @Kludex opinions?

It seems something is missing, since the user doesn't have access to the underlying ToolManager from the FastMCP object.

How is the user supposed to remove a tool?

@brandonspark
Copy link
Author

I think this is very reasonable. @Kludex opinions?

It seems something is missing, since the user doesn't have access to the underlying ToolManager from the FastMCP object.

How is the user supposed to remove a tool?

I was under the impression that one could obtain it from doing mcp._tool_manager from a ToolManager object. I assume your objection is because that field is supposed to be "private", but it would enable my use case. Do you have a significant objection?

It feels natural to me that this code should exist, as add_tool also exists.

@Kludex
Copy link
Member

Kludex commented Sep 2, 2025

That's a significant objection.

@brandonspark
Copy link
Author

That's a significant objection.

Then, as a maintainer, do you have thoughts on how you would rather enable this behavior? I am not as familiar with your project.

It seems reasonably clear to me that a user may want to alter the tools which are available to the MCP, in a way that is not solely static. I don't see why the tool manager must be private, nor why we could not surface some of these methods through the API.

@Kludex
Copy link
Member

Kludex commented Sep 2, 2025

I think the current implementation (besides my comment about the exceptions) makes sense. It's just incomplete.

Is the idea here to disable the tool for the whole app lifecycle or for the session? Does it even makes sense to have it disable for the app lifecycle, since the app may be running in multiple machines? I guess we want to expose a method you can call from the Context object anyway.

@Kludex
Copy link
Member

Kludex commented Sep 2, 2025

I don't think ToolManager should be exposed.

@brandonspark
Copy link
Author

brandonspark commented Sep 2, 2025

I think the current implementation (besides my comment about the exceptions) makes sense. It's just incomplete.

Is the idea here to disable the tool for the whole app lifecycle or for the session? Does it even makes sense to have it disable for the app lifecycle, since the app may be running in multiple machines? I guess we want to expose a method you can call from the Context object anyway.

In my use case, it would actually be most ideal if a tool could be enabled or disabled for specific sessions (meaning, differing tools for differing consumers of a single server). I wanted to downscope this, as this is certainly more complicated than I feel I am seeking to implement right now. My understanding is that currently ToolManager is "global" for the entire app.

In my use case, I want users who are hosting their own servers (instances of the application) to be able to decide if they want to allow certain tools to be available to the consumers of their server. The best way to do this currently is with conditional declaration, which I view to be quite heavy handed.

If we do not expose ToolManager, we could expose a method which makes use of remove_tool, but I don't see a place in the current API (a particular class, specifically) where that makes sense.

@brandonspark
Copy link
Author

Oh, I now see that FastMCP exposes a thin wrapper around add_tool. Any objections to me doing the same for remove_tool?

class FastMCP(Generic[LifespanResultT]):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants